#include "Auxiliar.h"

//a comunicação é análoga a leitura e escrita em arquivos
void enviar(int socketEnvio, int *vet, char *nomeHost) { //novamente, a comunicação é análoga a leitura e escrita em arquivos

    char buffer[256];
    struct timespec t1, t2;

    //captura o tempo 't1' do inicio do envio
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t1);

    //envia para o servidor de nivel superior 'vet', que possui os dados gerados 
    if (write(socketEnvio, vet, TAM_VET * sizeof (int)) < 0) {

        printf("\n\n >> %s: Erro ao escrever no socket.", nomeHost);
        close(socketEnvio);
        exit(1);

    } else {

        //"apaga" o buffer
        bzero(buffer, 256);
        
        //se o envio foi feito, le o buffer para obter a resposta do servidor
        if (read(socketEnvio, buffer, 255) < 0) {

            printf("\n\n >> %s: Erro ao ler do socket", nomeHost);
            close(socketEnvio);
            exit(1);

        } else {

            //captura o tempo 't2' do fim do envio e calcula a diferenca entre 't1' e 't2', obtendo portanto, o tempo total de envio
            clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t2);
            printf("\n\n >> %s: Resposta do servidor: %s", nomeHost, buffer);
            printf("\n\n >> %s: Tempo de comunicacao: %ld s e %ld ns", nomeHost, (long int)diferencaEntreInstantes(t1, t2).tv_sec, (long int)diferencaEntreInstantes(t1, t2).tv_nsec);
        }
    }
}

void receber(int socketRecepcao, int *vetTotal, char *nomeHost) {

    char bufferTxt[256];
    int j, bufferVet[NUM_CLIENTES_NIVEL_1 * TAM_VET], tamLido = 0, k = 0;

    //escreve a confirmacao de recebimento no bufferTxt que sera encaminhado ao servidor quando o vetor de dados do servidor chegar
    strcpy(bufferTxt, " @");
    strcat(bufferTxt, nomeHost);
    strcat(bufferTxt, ": Vetor total recebido.");

    //le o vetor de dados transmitido pelo servidor, armazena temporariamente em bufferVet e depois em vetTotal
    while (tamLido < NUM_CLIENTES_NIVEL_1 * TAM_VET * sizeof (int)) {

        k = read(socketRecepcao, bufferVet, (NUM_CLIENTES_NIVEL_1 * TAM_VET - tamLido) * sizeof (int));

        if (k <= 0) {

            printf("\n\n >> %s: Erro ao ler do socket.", nomeHost);
            close(socketRecepcao);
            exit(1);
        }
        else {

            for (j = 0; j < k / sizeof (int); j++) {

                vetTotal[tamLido / sizeof (int) +j] = bufferVet[j];
            }
        }

        tamLido += k;
    }

    //escreve no buffer a confirmacao do recebimento do vetor de dados
    if (write(socketRecepcao, bufferTxt, strlen(bufferTxt) * sizeof (char)) < 0) {

        printf("\n\n >> %s: Erro ao escrever no socket.", nomeHost);
        close(socketRecepcao);
        exit(1);
    }
}

int main(int argc, char** argv) {

    int socketEnvio, socketEspera, socketRecepcao, vet[TAM_VET], vetTotal[TAM_VET * NUM_CLIENTES_NIVEL_1], i;
    struct timespec tinic, tfinal;
    char nomeHost[15];
    struct sockaddr_in endServidorEnvio, endCliente, endServidorEspera;
    socklen_t tamEndCliente;

    gethostname(nomeHost, 15);

    printf("\n\n ******* Cliente - %s *******", nomeHost);

    socketEnvio = socket(AF_INET, SOCK_STREAM, 0); //análogo ao código do servidor

    if (socketEnvio < 0) {

        printf("\n\n >> %s: Erro ao abrir socket.", nomeHost);
        exit(1);
    }

    inet_aton(argv[1], &endServidorEnvio.sin_addr);
    endServidorEnvio.sin_family = AF_INET; //também análogo ao código do servidor
    endServidorEnvio.sin_port = htons(NUM_PORTA_INICIAL + 2);

    srand(time(NULL) + getpid());

    printf("\n\n >> %s: Vetor gerado: ", nomeHost);

    //gera o vetor de numeros aleatorios
    for (i = 0; i < TAM_VET; i++) {

        vet[i] = 1 + rand() % 1000;
        printf("%d ", vet[i]);
    }
    
    //armazena o tempo 'tinic' para posterior comparacao
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);
    
    qsort(vet, TAM_VET, sizeof (int), comparar);
    
    //armazena o tempo 'tfinal' para avaliar o tempo de ordenacao
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);
    
    printf("\n\n >> %s: Tempo de ordenacao: %ld s e %ld ns", nomeHost, (long int)diferencaEntreInstantes(tinic, tfinal).tv_sec, (long int)diferencaEntreInstantes(tinic, tfinal).tv_nsec);

    //connect() faz a requisição de conexão ao servidor especificado; o servidor deverá estar esperando, através da função accept()
    //tenta estabelecer uma comunicacao com o servidor de nivel superior; tendo como argumentos (descritor de socket, endereco e porta do servidor, tamanho do endereco)
    if (connect(socketEnvio, (struct sockaddr *) &endServidorEnvio, sizeof (endServidorEnvio)) < 0) { //connect() faz a requisição de conexão ao servidor no endereço e porta especificados; o servidor deverá estar esperando em um socket nessa porta, através da função accept(). Em nenhum momento especificamos o número da porta usada no host do cliente: isso é feito automaticamente pelo SO

        //calcula o tempo total de ordenacao, baseaso em 'tinic' e 'tfinal'
        printf("\n\n >> %s: Erro ao estabelecer conexao.", nomeHost);
        close(socketEnvio);
        exit(1);

    } else
        //se a conexao foi feita com sucesso, envia o vetor para o proximo nivel
        enviar(socketEnvio, vet, nomeHost);

    close(socketEnvio);

    // ********    Servidor   *******************************************************
    //Os clientes de nivel 1 comportam-se momentaneamente como servidores ao esperar conexao do nivel 4 para o recebimento do vetor final

    //"zera" a variavel que armazena o endereco do servidor
    bzero((char *) &endServidorEspera, sizeof (endServidorEspera));

    endServidorEspera.sin_family = AF_INET; //AF_INET: enderecos serao do padrao da internet
    endServidorEspera.sin_port = htons(NUM_PORTA_INICIAL + 1);  //htons() converte o número da porta para o formato (big endian) usado nas redes
    endServidorEspera.sin_addr.s_addr = INADDR_ANY;

    socketEspera = socket(AF_INET, SOCK_STREAM, 0);

    //serve para associar o socket a porta escolhida, com os parametros especificados
    if (bind(socketEspera, (struct sockaddr *) &endServidorEspera, sizeof (endServidorEspera)) < 0) { //serve para "prender" o socket à porta escolhida, com os parâmetros especificados

        printf("\n\n >> %s: Erro ao associar o socket ao endereco.", nomeHost);

        if (errno == EADDRINUSE) {

            printf(" Endereco ja esta em uso.");
        }

        close(socketEspera);
        exit(1);
    }

    //faz com que o servidor "ouca" constantemente o socket a espera de requisições; o 5 é o numero maximo de requisicoes que poderao esperar na fila de atendimento
    if (listen(socketEspera, 5) == -1) { //faz com que o servidor "ouça" constantemente o socket à espera de requisições; o 5 é o número máximo de requisições que poderão esperar na fila de atendimento

        printf("\n\n >> %s: Erro ao ouvir o estado de conexao do socket.", nomeHost);
        close(socketEspera);
        exit(1);
    }

    printf("\n\n >> %s: Esperando por requisicoes na porta %d. \n", nomeHost, NUM_PORTA_INICIAL + 1);
    fflush(stdout);

    tamEndCliente = sizeof (endCliente);

    //accept() [abaixo] faz com que o cliente aceite pedidos de conexao realizados na porta associada; enquanto nenhuma requisicao é feita, o processo fica bloqueado
    //Quando uma requisição é aceita, um novo socket é criado para a comunicacao, com descritor socketRecepcao
    socketRecepcao = accept(socketEspera, (struct sockaddr *) &endCliente, &tamEndCliente);

    //executa a funcao para receber dados
    receber(socketRecepcao, vetTotal, nomeHost);
    
    //armazena o tempo 'tfinal' para comparacao
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

    printf("\n\n >> %s: Vetor total ordenado: ", nomeHost);

    for (i = 0; i < NUM_CLIENTES_NIVEL_1 * TAM_VET; i++) {

        printf("%d ", vetTotal[i]);
    }
    
    //imprime o tempo total de execucao baseado nos tempos 'tinic' e 'tfinal'
    printf("\n\n >> %s: Tempo total de execucao: %ld s e %ld ns", nomeHost, (long int)diferencaEntreInstantes(tinic, tfinal).tv_sec, (long int)diferencaEntreInstantes(tinic, tfinal).tv_nsec);

    close(socketRecepcao);
    close(socketEspera);
    
    printf("\n\n");

    return (0);
}